using LightCAD.MathLib;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace LightCAD.Three
{
    public class Raycaster
    {
        public class Intersection
        {
            public int index;
            public double distance;
            public Vector3 point;
            public Object3D target;
            public Vector2 uv;
            public Vector2 uv2;
            public Vector3 normal;
            public Face3 face;
            public int faceIndex;
            public double distanceToRay;
            public Dictionary<string, Object> ext;
            public int instanceId;
            public Vector3 pointOnLine;//lineSegments
        }

        public class RaycasterParams : JsObj<object>
        {
            public JsObj<object> Mesh = new JsObj<object>();
            public JsObj<double> Line = new JsObj<double>() { { "threshold", 1 } };
            public JsObj<double> Line2 = null;
            public JsObj<object> LOD = new JsObj<object>();
            public JsObj<double> Points = new JsObj<double>() { { "threshold", 1 } };
            public JsObj<object> Sprite = new JsObj<object>();
        }

        #region Properties
        public Ray ray { get; set; }
        public double near;
        public double far;
        public Camera camera;
        public Layers layers;
        public RaycasterParams _params;

        #endregion

        #region constructor
        public Raycaster(Vector3 origin = null, Vector3 direction = null, double near = 0, double far = double.PositiveInfinity)
        {
            this.ray = new Ray(origin, direction);
            // direction is assumed to be normalized (for accurate distance calculations)
            this.near = near;
            this.far = far;
            this.camera = null;
            this.layers = new Layers();
            this._params = new RaycasterParams();
        }
        #endregion

        #region methods

        public void set(Vector3 origin, Vector3 direction)
        {
            // direction is assumed to be normalized (for accurate distance calculations)
            this.ray.Set(origin, direction);
        }
        public Raycaster setFromCamera(Vector2 coords, Camera camera)
        {
            if (camera is PerspectiveCamera)
            {
                this.ray.Origin.SetFromMatrixPosition(camera.matrixWorld);
                this.ray.Direction.Set(coords.X, coords.Y, 0.5).Unproject(camera).Sub(this.ray.Origin).Normalize();
                this.camera = camera;
            }
            else if (camera is OrthographicCamera)
            {
                var ocamera = camera as OrthographicCamera;
                this.ray.Origin.Set(coords.X, coords.Y, (ocamera.near + ocamera.far) / (ocamera.near - ocamera.far)).Unproject(camera); // set origin in plane of camera
                this.ray.Direction.Set(0, 0, -1).TransformDirection(camera.matrixWorld);
                this.camera = camera;
            }
            else
            {
                console.error("THREE.Raycaster: Unsupported camera type: " + camera.GetType());
            }
            return this;
        }


        public ListEx<Intersection> intersectObject(Object3D _object, bool recursive = true, ListEx<Intersection> intersects = null)
        {
            if (intersects == null)
                intersects = new ListEx<Intersection>();
            doIntersectObject(_object, intersects, recursive);
            intersects.Sort(ascSort);
            return intersects;
        }

        public ListEx<Intersection> intersectObjects(IList<Object3D> objects, bool recursive = true, ListEx<Intersection> intersects = null)
        {
            intersects = intersects ?? new ListEx<Intersection>();
            for (int i = 0, l = objects.Count; i < l; i++)
            {
                doIntersectObject(objects[i], intersects, recursive);
            }
            intersects.Sort(ascSort);
            return intersects;
        }


        private void doIntersectObject(Object3D _object, ListEx<Intersection> intersects, bool recursive)
        {
            //by rider
            //if (_object is IMergedObject) return;
            if (_object.layers.test(this.layers))
            {
                _object.raycast(this, intersects);
            }

            if (recursive)
            {
                var children = _object.children;
                for (int i = 0, l = children.Length; i < l; i++)
                {
                    doIntersectObject(children[i], intersects, true);
                }
            }
        }

        private int ascSort(Intersection a, Intersection b)
        {
            var delta = a.distance - b.distance;
            return delta == 0 ? 0 : delta > 0 ? 1 : -1;
        }
        #endregion
    }
}
